package com.qfedu.edu.payservice.service.impl;
import com.alibaba.fastjson.JSON;
import com.qfedu.edu.bo.OrderShow;
import com.qfedu.edu.bo.QueueMessage;
import com.qfedu.edu.bo.ReduceStock;
import com.qfedu.edu.bo.UpdateOrderStatus;
import com.qfedu.edu.constance.RabbitMQConstance;
import com.qfedu.edu.constance.RedisConstance;
import com.qfedu.edu.enum1.BuyStatus;
import com.qfedu.edu.exception.BusinessException;
import com.qfedu.edu.manager.RabbitMQManager;
import com.qfedu.edu.manager.RedisManager;
import com.qfedu.edu.payservice.service.PayService;
import com.qfedu.edu.payservice.utils.PayCommonUtil;
import com.qfedu.edu.payservice.utils.PayConfigUtil;
import com.qfedu.edu.payservice.utils.XMLUtil;
import com.qfedu.edu.result.ResponseCode;
import com.qfedu.edu.vo.PayVo;
import lombok.extern.slf4j.Slf4j;
import org.jdom.JDOMException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaobobo
 * @title: PayServiceImpl
 * @projectName seckill-fy-2401-parent
 * @description: TODO
 * @date 2024/11/14  9:17
 */
@Service
@Slf4j
public class PayServiceImpl implements PayService {

    @Autowired
    private RedisManager redisManager;

    @Autowired
    private RabbitMQManager rabbitMQManager;

    @Override
    public String pay(PayVo payVo) throws Exception {
        //校验参数
        checkParams(payVo);
        //就是分类进行处理
        switch (payVo.getType()) {
            case 0:
                return weixinPay(payVo);
            case 1:
                return aliPay(payVo);
            case 2:
                return unionPay(payVo);
        }
        return null;
    }

    @Override
    public void weixinPayNotify(HttpServletRequest req, HttpServletResponse resp) throws IOException, JDOMException {
        //获取微信服务器的消息
        SortedMap<Object, Object> packageParams = getWeixinServerMessage(req);
        // 账号信息
        String key = PayConfigUtil.API_KEY; // key
        //这里获取到的就是咋们的订单号
        String orderNunber = (String) packageParams.get("out_trade_no");//订单号,实际开发中应该在下⾯的 IF 中, 除⾮需要对每个订单的每次⽀付结果做记录
        //判断签名是否正确
        if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
            String resXml = "";
            if ("SUCCESS".equals((String) packageParams.get("result_code"))) { //支付成功的
                //支付成功之后 还需要向Redis写入支付状态  支付状态如何存储呢? pay:status:订单号 0/1
                //构建这个key
                String payStatusKey = RedisConstance.getPayStatusPrefix() + orderNunber;
                //接下来向Redis写入状态
                redisManager.addObject(payStatusKey, 0, 60, TimeUnit.DAYS);
                //去MySQL中实现真正的减库存 (这个位置是存在高并发的) 因为支付的速度远远大于咋们写MySQL的速度
                //所以这里存在高并发  前后速度不匹配的问题 那么这里怎么办呢? 要借助于咋们的MQ 来削峰填谷
                // 也就是说 不管前端的请求有多大 我都慢慢的减库存  多余的请求存储到MQ中 进行缓冲...
                //要向队列中发送消息的话 那么首先我们要 封装消息
                String queueMessageJsonStr = getStockQueueMessage(orderNunber);
                //向队列中发送消息
                rabbitMQManager.sendWork(RabbitMQConstance.getStockReduceName(), queueMessageJsonStr);
                log.info("减库存的队列消息已经放到了MQ...");

                //去MySQL中修改订单状态
                //修改订单状态也需要处理高并发  这里的高并发场景也需要采用MQ的消费端限流去实现...
                //这里的逻辑就能说清楚了和上面一致...
                //需要将消息和咋们的这个订单队列中的消息放到一起去 然后还要判断这个消息是否存在
                String queueMessageJsonStr1 = getUpdateOrderStatusQueueMessageStr(orderNunber, 1);
                rabbitMQManager.sendWork(RabbitMQConstance.getOrderName(), queueMessageJsonStr1);


                //这里需要去修改订单的秒杀状态
                //这里还需要去修改用户的秒杀状态
                updateSellStatus(orderNunber,BuyStatus.SUCCESS);
                log.info("修改订单的的队列消息已经放到了MQ...");
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]> </return_code > "
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";

            } else {   //支付失败
                //支付的状态 写入到redis中 告诉前端支付失败了....
                //构建这个key
                String payStatusKey = RedisConstance.getPayStatusPrefix() + orderNunber;
                //接下来向Redis写入状态
                redisManager.addObject(payStatusKey, 1, 60, TimeUnit.DAYS);

                //MySQL中的订单状态也需要修改...
                //去MySQL中修改订单状态
                //修改订单状态也需要处理高并发  这里的高并发场景也需要采用MQ的消费端限流去实现...
                //这里的逻辑就能说清楚了和上面一致...
                //需要将消息和咋们的这个订单队列中的消息放到一起去 然后还要判断这个消息是否存在
                String queueMessageJsonStr1 = getUpdateOrderStatusQueueMessageStr(orderNunber, 2);
                rabbitMQManager.sendWork(RabbitMQConstance.getOrderName(), queueMessageJsonStr1);


                //这里还需要去修改用户的秒杀状态
                updateSellStatus(orderNunber,BuyStatus.FAIL);
                //还要将这个库存还原....
                reuseStock(orderNunber);

                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[报⽂为空]]></return_msg>" + "</xml> ";
            }
            //向微信的服务器返回数据
            sendResponse(resp, resXml);
        } else {
            log.error("订单号:{}签名校验失败", orderNunber);
        }
    }

    /**
     * 还原Redis中的这个库存
      * @param orderNumber
     */
    private void reuseStock(String orderNumber) {
        //找到Redis中的库存数据
        OrderShow orderBo = listRedisOrder(orderNumber);
        //就可以拿到这个产品的id
        String classId = orderBo.getClassId();
        String key=RedisConstance.getBuyCountPrefix()+classId;
        //现在需要-1
        redisManager.incr(key,-1);
    }


    /**
     * 在这里需要去修改这个商品的秒杀状态
     * @param orderNum
     */
    private void updateSellStatus(String orderNum, BuyStatus buyStatus) {
        OrderShow orderShow = listRedisOrder(orderNum);
        //接下来就可以拿到我们的信息了...
        String userId = orderShow.getUserId();
        //还要拿到这个课程的id
        String classId = orderShow.getClassId();
        String key=RedisConstance.getBuyStatusPrefix()+userId+":"+classId;
        //接下来更新这个数据
        redisManager.addObject(key,buyStatus.getStatus(),60,TimeUnit.DAYS);
    }



    /**
     * 从redis中去获取这个订单的信息
     * @param orderNum
     * @return
     */
    private OrderShow listRedisOrder(String orderNum) {
        //需要通过这个ke将这个数据给拿回来
        String jsonStr = redisManager.getString(orderNum);
        //接下来就可以转换这个对象了
        OrderShow orderShow = JSON.parseObject(jsonStr, OrderShow.class);
        return orderShow;
    }



    @Override
    public Integer getPayStatus(String orderNumber) {
        if(StringUtils.isEmpty(orderNumber)){
           throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
        //执行到这里说明没有问题
        //构建redis中获取数据的key
        String payStatusKey = RedisConstance.getPayStatusPrefix() + orderNumber;
        //获取支付的状态
        Integer status = (Integer) redisManager.getObject(payStatusKey);
        if(null==status){
            return -1;
        }
        return status;
    }

    /**
     * 组装修改订单状态的这个队列消息
     *
     * @param orderNumber
     * @return
     */
    private String getUpdateOrderStatusQueueMessageStr(String orderNumber, Integer status) {
        UpdateOrderStatus updateOrderStatus = new UpdateOrderStatus();
        updateOrderStatus.setOrderNumber(orderNumber);
        // 0:正在秒杀  1：支付成功   2：支付失败...
        updateOrderStatus.setStatus(status);
        QueueMessage queueMessage = new QueueMessage();
        queueMessage.setMessage(JSON.toJSONString(updateOrderStatus));
        queueMessage.setQueueName(RabbitMQConstance.getOrderName());
        //1：表示的是修改订单状态
        queueMessage.setType(0);
        return JSON.toJSONString(queueMessage);
    }

    /**
     * 封装库存的队列消息
     *
     * @param orderNumber
     * @return
     */
    private String getStockQueueMessage(String orderNumber) {
        //申明一个队列消息对象
        QueueMessage queueMessage = new QueueMessage();
        //接下来就可以设置这个消息了
        ReduceStock reduceStock = new ReduceStock();
        reduceStock.setOrderNumber(orderNumber);
        reduceStock.setCount(1);
        String reduceStockJsonStr = JSON.toJSONString(reduceStock);
        //将这个消息放到咋们的这个队列消息中去
        queueMessage.setMessage(reduceStockJsonStr);
        queueMessage.setQueueName(RabbitMQConstance.getStockReduceName());
        //2表示的是减库存
        queueMessage.setType(2);
        return JSON.toJSONString(queueMessage);
    }

    private static void sendResponse(HttpServletResponse resp, String resXml) throws IOException {
        //------------------------------
        //处理业务完毕
        //------------------------------
        BufferedOutputStream out = new BufferedOutputStream(
                resp.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    }

    private static SortedMap<Object, Object> getWeixinServerMessage(HttpServletRequest req) throws IOException, JDOMException {
        //这里是从请求中获取腾讯服务器 传输过来的参数
        InputStream inputStream;
        StringBuffer sb = new StringBuffer();
        inputStream = req.getInputStream();
        String s;
        BufferedReader in = new BufferedReader(
                new InputStreamReader(inputStream,
                        "UTF-8"));
        while ((s = in.readLine()) != null) {
            sb.append(s);
        }
        in.close();
        inputStream.close();

        //解析xml成map
        Map<String, String> m = new HashMap<String, String>();
        m = XMLUtil.doXMLParse(sb.toString());

        //过滤空 设置 TreeMap
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        Iterator it = m.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = m.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        return packageParams;
    }

    /**
     * 银联支付的处理方式
     *
     * @param payVo
     * @return
     */
    private String unionPay(PayVo payVo) {
        return null;
    }

    /**
     * 支付宝支付的处理方式
     *
     * @param payVo
     * @return
     */
    private String aliPay(PayVo payVo) {
        return null;
    }

    /**
     * 微信支付的处理方式
     *
     * @param payVo
     * @return
     */
    private String weixinPay(PayVo payVo) throws Exception {
        //下面这个方法中
        //最终返回了一个 url信息  这个url就是我们的二维码的信息
        String url = PayCommonUtil.weixin_pay(payVo.getPrice(), payVo.getClassName(), payVo.getOrderNumber());//获取微信
        return url;
    }

    /**
     * 校验参数的合法性
     *
     * @param payVo
     */
    private void checkParams(PayVo payVo) {
        if (null == payVo || StringUtils.isEmpty(payVo.getOrderNumber()) || null == payVo.getType() || StringUtils.isEmpty(payVo.getPrice()) || StringUtils.isEmpty(payVo.getClassName())) {
            throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
    }

}
